# -*- coding: utf-8 -*- 2
"""
2020-5-7更新
六角格网格（Hexagonal Grids）相关的类
计算方法的说明见文档：Manu_hexagonal_map.pdf（Zotero/六角格地图）
"""

__author__ = 'Yu XH'

#import common
import re


class HexOffset:
    """六角格的偏移坐标类"""
    row = None
    col = None

    def __init__(self, y: int, x: int):
        assert y >= 0 and x >= 0
        self.row = y
        self.col = x

    def __str__(self):
        return 'row = %d, col = %d' % (self.row, self.col)

    def __hash__(self):
        # hash tuple to avoid calling .value / string stuff which is expensive
        # and may never need to be calculated.
        return hash((self.row, self.col))
        if not hasattr(self, '_value'):
            self._value = hash((self.row, self.col))
        return self._value

    def __eq__(self, other):
        try:
            if isinstance(other, HexOffset):
                # Compare this and not .value, much faster.
                return self.row == other.row and self.col == other.col

            elif len(other) == 2:
                row, col = other[0], other[1]
            return self.row == int(row) and self.col == int(col)
        except (TypeError, IndexError, ValueError):
            return False

    def __ne__(self, other): # 不等于
        return not self.__eq__(other)

    def __lt__(self, other):   # 小于
        try:
            if isinstance(other, HexOffset):
                # Compare this and not .value, much faster.
                return self.row < other.row and self.col < other.col

            elif len(other) == 2:
                row, col = other[0], other[1]
            return self.row < int(row) and self.col < int(col)
        except (TypeError, IndexError, ValueError):
            return False

    def __le__(self, other): # 小于等于
        try:
            if isinstance(other, HexOffset):
                # Compare this and not .value, much faster.
                return self.row <= other.row and self.col <= other.col

            elif len(other) == 2:
                row, col = other[0], other[1]
            return self.row <= int(row) and self.col <= int(col)
        except (TypeError, IndexError, ValueError):
            return False

    def __gt__(self, other): # 大于
        try:
            if isinstance(other, HexOffset):
                # Compare this and not .value, much faster.
                return self.row > other.row and self.col > other.col

            elif len(other) == 2:
                row, col = other[0], other[1]
            return self.row > int(row) and self.col > int(col)
        except (TypeError, IndexError, ValueError):
            return False

    def __ge__(self, other): # 大于等于
        try:
            if isinstance(other, HexOffset):
                # Compare this and not .value, much faster.
                return self.row >= other.row and self.col >= other.col

            elif len(other) == 2:
                row, col = other[0], other[1]
            return self.row >= int(row) and self.col >= int(col)
        except (TypeError, IndexError, ValueError):
            return False


    def is_same(self, hex_t):
        """当前六角格与hex_t是相同六角格的判断"""
        if abs(self.row - hex_t.row) <= 1e-8 and abs(self.col - hex_t.col) <= 1e-8:
            return True

    def cvt_2_cube(self):
        """偏移坐标转换到Cube坐标"""
        try:
            assert self.col >= 0 and self.row >= 0
            a = self.col - (self.row - (int(self.row) & 1)) // 2
            c = self.row
            b = 0 - a - c
            return HexCube(a, b, c)
        except Exception as e:
            # common.echosentence_color(
            #     'error in class HexOffset cvt_2_cube():[row:col={}:{}]:{}'.format(self.col, self.row, str(e)))
            raise

    def get_neighbors(self, directions=None):
        """
        返回指定方向(多个)上的邻格
        :param directions: (list)方向代号，取值0,1,2,3,4,5，0表示正东方向，然后依次顺时针取
        :return: 在direction_ind方向上的邻格，由HexOffset对象构成的List
        """
        if directions is None:
            directions = range(6)  # 计算所有的邻格
        try:
            assert self.col >= 0 and self.row >= 0 and len(directions) <= 6
            directions_offset_odd = [(1, 0), (1, 1), (0, 1), (-1, 0), (0, -1), (1, -1)]
            directions_offset_even = [(1, 0), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1)]
            list_neighbors = []
            is_even = self.row % 2 == 1
            directions_offset = directions_offset_even if is_even else directions_offset_odd
            for direct in directions:
                assert 0 <= direct < 6
                list_neighbors.append(HexOffset(self.row+directions_offset[direct][0],
                                                self.col+directions_offset[direct][1]))
            return list_neighbors
        except Exception as e:
            # common.echosentence_color('error in class HexOffset get_neighbors():{}'.format(str(e)))
            raise

    def calc_distance(self, hex_t: 'HexOffset'):
        """
        计算当前六角格到六角格a_hex的距离
        :param hex_t: 偏移坐标表示的六角格
        :return: 距离结果
        """
        hex_s_cube = self.cvt_2_cube()
        hex_t_cube = hex_t.cvt_2_cube()
        return hex_s_cube.calc_distance(hex_t_cube)

    def get_wedge_direction(self, hex_t: 'HexOffset'):
        """
        确定六角格hex_t位于当前六角格的那个楔形方向，0表示正东方向
        :param hex_t: 偏移坐标表示的六角格
        :return: list[0,...,5]
        """
        hex_s_cube = self.cvt_2_cube()
        hex_t_cube = hex_t.cvt_2_cube()
        return hex_s_cube.get_wedge_direction(hex_t_cube)

    def get_range(self, r: 'int >= 0'):
        """
        找出所有距当前六角格r范围里面的六角格
        :param r: 范围的半径
        :return: 范围里面所有六角格构成的List
        """
        hex_cube = self.cvt_2_cube()
        hex_range_cube = hex_cube.get_range(r)
        hex_range = []
        for h in hex_range_cube:
            h_offset = h.cvt_2_offset()
            if h_offset is not None:
                if h_offset.row >= 0 and h_offset.col >= 0:
                    hex_range.append(h_offset)
        return hex_range

    def get_arc(self, radius, direction):
        """
        生成指定方向上、确定半径下的圆弧
        :param radius: 距当前格的半径
        :param direction: 当前格的楔形方向，0-5, 0表示正东方向
        :return: 圆弧上所有六角格构成的列表
        """
        hex_cube = self.cvt_2_cube()
        hex_arc_cube = hex_cube.get_arc(radius, direction)
        hex_arc = []
        if hex_arc_cube:
            for h in hex_arc_cube:
                h_offset = h.cvt_2_offset()
                if h_offset is not None:
                    if h_offset.row >= 0 and h_offset.col >= 0:
                        hex_arc.append(h_offset)
        return hex_arc

    def get_line(self, hex_t: 'HexOffset'):
        """输出一条直线，包含起始格和终止格"""
        hex_s_cube = self.cvt_2_cube()
        hex_t_cube = hex_t.cvt_2_cube()
        line = []
        line_cube = hex_s_cube.get_line(hex_t_cube)
        for h in line_cube:
            line.append(h.cvt_2_offset())
        return line


class HexCube:
    """六角格的Cube坐标类"""
    a = None
    b = None
    c = None

    def __init__(self, x, y, z):
        self.a = x
        self.b = y
        self.c = z

    def is_valid(self):
        """判断Cube坐标是否有效，即3个坐标值是否都是整数，且3个坐标值之和等于0"""
        re_str = '^-?\d+\.?0*$'
        # 使用正则表达式进行判断，诸如23.0的浮点型整数也会正确判断成整数
        if re.match(re_str, str(self.a)) is not None:
            if re.match(re_str, str(self.b)) is not None:
                if re.match(re_str, str(self.c)) is not None:
                    if abs(self.a+self.b+self.c) <= 1e-8:
                        return True

    def cube_directions(self, direction):
        cube_directions = [HexCube(1, -1, 0),
                            HexCube(0, -1, 1),
                            HexCube(-1, 0, 1),
                            HexCube(-1, 1, 0),
                            HexCube(0, 1, -1),
                            HexCube(1, 0, -1)]
        return cube_directions[direction]

    def cvt_2_offset(self):
        """Cube坐标转换到偏移坐标"""
        try:
            if not self.is_valid():
                self_round = self.cube_round()
                self.a, self.b, self.c = self_round.a, self_round.b, self_round.c
            row = self.c
            col = self.a + (self.c - (int(self.c) & 1)) // 2
            return HexOffset(row, col)
        except Exception as e:
            # common.echosentence_color('error in class HexCube cvt_2_offset():{}'.format(str(e)))
            pass

    def cube_add(self, hex_cube: 'HexCube'):
        """cube坐标加法"""
        return HexCube(self.a + hex_cube.a,
                       self.b + hex_cube.b,
                       self.c + hex_cube.c)

    def cub_multiply(self, coeff):
        """cube坐标*常系数"""
        return HexCube(self.a * coeff, self.b * coeff, self.c * coeff)

    def cube_round(self):
        """cube坐标的四舍五入"""
        a_bar, b_bar, c_bar = round(self.a), round(self.b), round(self.c)
        e_a, e_b, e_c = abs(self.a - a_bar), abs(self.b - b_bar), abs(self.c - c_bar)
        if e_a >= e_b and e_a >= e_c:
            return HexCube(-b_bar-c_bar, b_bar, c_bar)
        elif e_a < e_b and e_b >= e_c:
            return HexCube(a_bar, -a_bar-c_bar, c_bar)
        else:
            return HexCube(a_bar, b_bar, -a_bar-b_bar)

    def neighbor(self, direction):
        """获得当前六角格的邻格"""
        return self.cube_add(self.cube_directions(direction))

    def calc_distance(self, hex_t: 'HexCube'):
        """
        计算当前六角格到六角格a_hex的距离
        :param hex_t: Cube坐标表示的六角格
        :return: 距离结果
        """
        try:
            if not self.is_valid():
                self_round = self.cube_round()
                self.a, self.b, self.c = self_round.a, self_round.b, self_round.c
            if not hex_t.is_valid():
                hex_t = hex_t.cube_round()
            return max(abs(self.a-hex_t.a), abs(self.b-hex_t.b), abs(self.c-hex_t.c))
        except Exception as e:
            # common.echosentence_color('error in class HexCube calc_distance():{}'.format(str(e)))
            pass

    def get_wedge_direction(self, hex_t: 'HexCube'):
        """
        确定六角格hex_t位于当前六角格的那个楔形方向，0表示正东方向
        :param hex_t: Cube坐标表示的六角格
        :return: list[0,...,5]
        """
        try:
            if not self.is_valid():
                self_round = self.cube_round()
                self.a, self.b, self.c = self_round.a, self_round.b, self_round.c
            if not hex_t.is_valid():
                hex_t = hex_t.cube_round()
            delta_a = - self.a + hex_t.a
            delta_b = - self.b + hex_t.b
            delta_c = - self.c + hex_t.c
            abs_a_c = abs(delta_a - delta_c)
            abs_c_b = abs(delta_c - delta_b)
            abs_b_a = abs(delta_b - delta_a)
            # 位于0和5交界处的格子，归0方向；位于2和3交界处的格子，归3方向
            if abs_a_c == max(abs_a_c, abs_c_b, abs_b_a) and abs_a_c > abs_b_a:
                return 5 if delta_a >= delta_c else 2
            # 位于1和2交界处的格子，归2方向；位于4和5交界处的格子，归5方向
            if abs_c_b == max(abs_a_c, abs_c_b, abs_b_a) and abs_c_b > abs_a_c:
                return 1 if delta_c >= delta_b else 4
            # 位于3和4交界处的格子，归4方向；位于0和1交界处的格子，归1方向
            if abs_b_a == max(abs_a_c, abs_c_b, abs_b_a) and abs_b_a > abs_c_b:
                return 3 if delta_b >= delta_a else 0
        except Exception as e:
            # common.echosentence_color('error in class HexCube get_wedge_direction():{}'.format(str(e)))
            pass

    def get_range(self, r: 'int >= 0'):
        """
        找出所有距当前六角格r范围里面的六角格
        :param r: 范围的半径
        :return: 范围里面所有六角格构成的List
        """
        try:
            if not self.is_valid():
                self_round = self.cube_round()
                self.a, self.b, self.c = self_round.a, self_round.b, self_round.c
            hexes_range = []
            a = -r + self.a
            while a <= r + self.a:
                b = max(-r, -r-(a-self.a)) + self.b
                while b <= min(r, r-(a-self.a)) + self.b:
                    c = -a - b
                    hexes_range.append(HexCube(a, b, c))
                    b += 1
                a += 1
            return hexes_range
        except Exception as e:
            # common.echosentence_color('error in class HexCube get_range():{}'.format(str(e)))
            pass

    def get_arc(self, radius, direction):
        """ 2020-5-3
        找出指定方向和半径上的圆弧
        """
        try:
            if not self.is_valid():
                self_round = self.cube_round()
                self.a, self.b, self.c = self_round.a, self_round.b, self_round.c
            if radius == 0:
                return self
            hex_arc = []
            sides = {0: [2, 4], 1: [3, 5], 2: [4, 0], 3: [5, 1], 4: [0, 2], 5: [1, 3]}
            cube_direction = self.cube_directions(direction)
            cur_cube = self.cube_add(HexCube(radius*cube_direction.a,
                                             radius*cube_direction.b,
                                             radius*cube_direction.c))
            hex_arc.append(cur_cube)
            right = sides[direction][0]
            left = sides[direction][1]
            left_cube = cur_cube
            right_cube = cur_cube
            if radius % 2 == 0:
                num_left = int(radius / 2)
                num_right = num_left - 1
            else:
                num_left = int((radius-1) / 2)
                num_right = num_left
            if num_left > 0:
                for m in range(num_left):
                    left_cube = left_cube.neighbor(left)
                    hex_arc.append(left_cube)
            if num_right > 0:
                for m in range(num_right):
                    right_cube = right_cube.neighbor(right)
                    hex_arc.append(right_cube)
            return hex_arc
        except Exception as e:
            # common.echosentence_color('error in class HexCube get_arc():{}'.format(str(e)))
            pass

    def get_line(self, hex_t: 'HexCube'):
        """输出一条直线，包含终止格，不包含起始格"""
        try:
            if not self.is_valid():
                self_round = self.cube_round()
                self.a, self.b, self.c = self_round.a, self_round.b, self_round.c
            if not hex_t.is_valid():
                hex_t = hex_t.cube_round()
            line = []
            dist = self.calc_distance(hex_t)
            for n in range(1, dist):
                line.append(HexCube(self.a + 1.0 * n * (hex_t.a - self.a) / dist,
                                    self.b + 1.0 * n * (hex_t.b - self.b) / dist,
                                    self.c + 1.0 * n * (hex_t.c - self.c) / dist).cube_round()
                            )
            line.append(hex_t)
            return line
        except Exception as e:
            pass


class Directions:
    Cube = [HexCube(1, -1, 0),
            HexCube(0, -1, 1),
            HexCube(-1, 0, 1),
            HexCube(-1, 1, 0),
            HexCube(0, 1, -1),
            HexCube(1, 0, -1)]
